#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
#include <gccore.h>
#include <gccore.h>
#include <wiiuse/wpad.h>
#include <fat.h>
#include "syslog.h"
#include "menu_tik_bin.h"
#include "menu_tmd_bin.h"
#include "menu_certs_bin.h"
#ifndef __CRAZYVIDEO__
#include "crazyintroloader_bin.h"
#endif
#include "action.h"
#include "config.h"
#include "keys.h"
#include "syslog.h"
#include "elf.h"
#include "dol.h"
#include <ogc/machine/asm.h>
#include <ogc/machine/processor.h>

#define DOL_TO_LAUNCH	0x80002F00
#define EXECUTABLE_MEM_ADDR 0x92000000
#ifdef DEBUG
	syslog_instance_t *syslog;
	
	void actionSetDebugInstance(syslog_instance_t * syslogInstance) 
	{
		syslog = syslogInstance;
	}
	
#endif


typedef void (*entrypoint) (void);

void cleanup() 
{
	#ifdef DEBUG
		Syslog_Send(syslog, SYSLOG_PRI_LOCAL0, SYSLOG_SEV_DEBUG, "Cleaning up");	
	#endif
	WPAD_Shutdown();	
	#ifdef DEBUG
		Syslog_Send(syslog, 0, 0, "WIILOADSYSLOG:EXIT");
		Syslog_End(syslog);
	#endif
}

void exitToSystemMenu() 
{
	cleanup();
	usleep(500); 
	SYS_ResetSystem(SYS_RETURNTOMENU, 0, 0);	
}

void launchBootmii() 
{
	#define PATCHMII_IOS 254
    fatUnmount("sd");   
    sdio_Deinitialize();
	
	cleanup();
	usleep(500); 
	
	SystemMenuAuth();	
	
	s32 ret;
	ret = IOS_ReloadIOS(PATCHMII_IOS);
	sleep(5);
}


void SystemMenuAuth()
{
	int rest = ES_Identify((signed_blob*)menu_certs_bin, 
					menu_certs_bin_size, 
					(signed_blob*)menu_tmd_bin, 
					menu_tmd_bin_size, 
					(signed_blob*)menu_tik_bin, 
					menu_tik_bin_size, 
					0);
}

void CheckRetval(int retval)
{
	switch(retval)
	{
		case ES_EINVAL:
			printf("FAILED! (Invalid Argument)\n\tQuitting...\n");
			RetvalFail();
			break;

		case ES_ENOMEM:
			printf("FAILED! (Out of memory)\n\tQuitting...\n");
			RetvalFail();
			break;

		case ES_ENOTINIT:
			printf("FAILED! (Not Initialized)\n\tQuitting...\n");
			RetvalFail();
			break;

		case ES_EALIGN:
			printf("FAILED! (Not Aligned)\n\tQuitting...\n");
			RetvalFail();
			break;
	}
}

void RetvalFail()
{
	VIDEO_Flush();
	VIDEO_WaitVSync();
	printf("\n\nEnding ES!... ");

	int retval2=__ES_Close();

	if(retval2!=0)
	{
		printf("FAILED!\n\tQuitting...\n");
	}
	exit(-1);
}


#ifndef __CRAZYVIDEO__

u32 loadExecutableFromMemNoExecute(void * exeBuffer) 
{
    u32 cookie;   
    int ret;
    
    int isExeValid           = 0;	
    u32 exeEntryPointAddress = 0;
	
    isExeValid = valid_elf_image(exeBuffer);

    if (isExeValid < 0) {
        printf("Not a valid elf file"); //Error handling
    }

	struct __argv args[10];

    if (isExeValid == 1) {
        exeEntryPointAddress = load_elf_image(exeBuffer);
    } else {
        exeEntryPointAddress = load_dol_image(exeBuffer, args);
    }
    
    if (exeEntryPointAddress == 0) {
		printf("Invalid entry point");
    }
	
	return exeEntryPointAddress;
}

void loadExecutableFromMem(void * exeBuffer) 
{
    u32 cookie;   
    int ret;
    
    int isExeValid           = 0;	
    u32 exeEntryPointAddress = 0;
    entrypoint exeEntryPoint;

    isExeValid = valid_elf_image(exeBuffer);

    if (isExeValid < 0) {
        printf("Not a valid elf file"); 
    }

	struct __argv args[10];

    if (isExeValid == 1) {
        exeEntryPointAddress = load_elf_image(exeBuffer);
    } else {
        exeEntryPointAddress = load_dol_image(exeBuffer, args);
    }
    
    if (exeEntryPointAddress == 0) {
		printf("Invalid entry point");
    }

    exeEntryPoint = (entrypoint) exeEntryPointAddress; 

    __IOS_ShutdownSubsystems ();
    _CPU_ISR_Disable (cookie);
    __exception_closeall ();
    exeEntryPoint ();
    _CPU_ISR_Restore (cookie);
}

void loadExecutable(char * exeFilePath) 
{
    u32 cookie;
    
    int ret;
    
	#ifdef DEBUG
		Syslog_SendFormat(syslog, SYSLOG_PRI_LOCAL0, SYSLOG_SEV_DEBUG, "Booting dol/elf:  %s", exeFilePath);
	#endif
	
    FILE *exeFile;
    void *exeBuffer          = (void *)EXECUTABLE_MEM_ADDR;
    int exeSize              = 0;
    int isExeValid           = 0;	
    u32 exeEntryPointAddress = 0;
    entrypoint exeEntryPoint;

	#ifdef DEBUG
		Syslog_Send(syslog, SYSLOG_PRI_LOCAL0, SYSLOG_SEV_DEBUG, "Opening file");
	#endif


	//Finally load the loader into loader area and launch(0x81330000)
	exeEntryPointAddress = loadExecutableFromMemNoExecute(crazyintroloader_bin);

	//Save the dol filename to launch into the exception vector area as a lame way of ipc...
	//We might be overwriting portions of hbc stub code (the last 256 bytes of the vector area)
	memcpy((char*)DOL_TO_LAUNCH, exeFilePath, 0x100);
	DCFlushRangeNoSync ((void*)DOL_TO_LAUNCH, 0x100);
	
	
	/*
    exeFile = fopen (exeFilePath ,"rb");

    if (exeFile==NULL) 
	{
		exitToSystemMenu();
		#ifdef DEBUG
			Syslog_Send(syslog, SYSLOG_PRI_LOCAL0, SYSLOG_SEV_DEBUG, "Cannot open file");
		#endif

    }
	
	#ifdef DEBUG
		Syslog_Send(syslog, SYSLOG_PRI_LOCAL0, SYSLOG_SEV_DEBUG, "Opening file");
	#endif


    fseek (exeFile, 0, SEEK_END);

	#ifdef DEBUG
		Syslog_Send(syslog, SYSLOG_PRI_LOCAL0, SYSLOG_SEV_DEBUG, "Seeked to end");
	#endif
	
    exeSize = ftell(exeFile);
	
	#ifdef DEBUG
		Syslog_SendFormat(syslog, SYSLOG_PRI_LOCAL0, SYSLOG_SEV_DEBUG, "Size : %d", exeSize);
	#endif
	
    fseek (exeFile, 0, SEEK_SET);
       
 	#ifdef DEBUG
		Syslog_Send(syslog, SYSLOG_PRI_LOCAL0, SYSLOG_SEV_DEBUG, "Seeked to beginning, reading into buffer");
	#endif
	
    if(fread (exeBuffer, 1, exeSize, exeFile) != exeSize) {	
		#ifdef DEBUG
			Syslog_Send(syslog, SYSLOG_PRI_LOCAL0, SYSLOG_SEV_DEBUG, "Error reading");
		#endif	
		exitToSystemMenu();		
    }

    fatUnmount("sd");   
    sdio_Deinitialize();
	*/

    fatUnmount("sd");   
    sdio_Deinitialize();
	
	WPAD_Shutdown();

    exeEntryPoint = (entrypoint) exeEntryPointAddress;
  
    __IOS_ShutdownSubsystems ();
    _CPU_ISR_Disable (cookie);
    __exception_closeall ();

    exeEntryPoint ();

    _CPU_ISR_Restore (cookie);
	
	

}


#endif


void launchTitle(u64 titleID, char* name, int need_sys)
{
	int retval;
	if(need_sys)
		SystemMenuAuth();

	VIDEO_Flush();
	VIDEO_WaitVSync();

	u32 cnt ATTRIBUTE_ALIGN(32);
	retval = ES_GetNumTicketViews(titleID, &cnt);
	CheckRetval(retval);
	tikview *views = (tikview *)memalign( 32, sizeof(tikview)*cnt );

	retval = ES_GetTicketViews(titleID, views, cnt);
	CheckRetval(retval);

	retval = ES_LaunchTitle(titleID, &views[0]);
	CheckRetval(retval);
}


void launchTitleWithId(u64 titleId, char* name) 
{
	//Identify as menu
	int rest = ES_Identify((signed_blob*)menu_certs_bin, menu_certs_bin_size, (signed_blob*)menu_tmd_bin, menu_tmd_bin_size, (signed_blob*)menu_tik_bin, menu_tik_bin_size, 0);
	
	__ES_Close();
	__ES_Init();		
	
	launchTitle(titleId, name, 1);	
}

void launchCrazyTitle(Title * title) 
{
	if (title == NULL) 
	{
		#ifdef DEBUG 
			Syslog_Send(syslog, SYSLOG_PRI_INTERNAL, SYSLOG_SEV_DEBUG, "syslog: Title is null, booting menu");		
		#endif	
		//Boot menu
		exitToSystemMenu();
	} else if (title->titleId!=0) 
	{
		#ifdef DEBUG 
			Syslog_Send(syslog, SYSLOG_PRI_INTERNAL, SYSLOG_SEV_DEBUG, "syslog: Title is not null, launching the channel");		
		#endif	
	
		//Launch a channel		
		launchTitleWithId(title->titleId, "");	
	} else if (title->titlePath!=NULL) 
	{
	#ifndef __CRAZYVIDEO__
		//Launch dol/elf
		loadExecutable(title->titlePath);	
	#else
		//Not supported in Crazy Intro Video since compiled with another fat library
		exitToSystemMenu();	
	#endif
	} else {
		//Boot menu, no configured items.
		exitToSystemMenu();
	}
}

void launchHBC() 
{
	//launchTitleWithId(0x0001000148415858LL, "");	
	launchTitleWithId(0x000100014A4F4449LL,"");
}


void handleKeyAction(s8 currentKey, bool doingAutoboot, bool useDefaultIntro, s8 hiddenShortcutIndex)
{ 
	//Launch menu for default intro if user pressed A
	if ((currentKey == _CFG_A) && (useDefaultIntro)) { exitToSystemMenu(); }
	
	//Launch HBC for default intro if user pressed B
	if ((currentKey == _CFG_B) && (useDefaultIntro)) { launchHBC(); }

	//Launch HBC for default intro if user pressed B
	if ((currentKey == _CFG_1) && (useDefaultIntro)) { launchBootmii(); }

	/* Determine title to launch */
	
	// If no sd card inserted boot menu for A, boot Homebrew channel for B (Handled above)
	
	// If sd card inserted and have defined shortcuts, then get the shortcut with
	// the key. If no shortcut for the selected key then boot Menu.
	// (? maybe this  behaviour should be changed and instead do nothing in the above loop)
	
	if (hiddenShortcutIndex>CANDIDATE_CONTINUE) 
	{
		#ifdef DEBUG 
			Syslog_Send(syslog, SYSLOG_PRI_INTERNAL, SYSLOG_SEV_DEBUG, "syslog: launching hidden shortcut");		
		#endif
		//Hidden shortcut launching
		HiddenShortcut * hiddenShortcut;
		hiddenShortcut = GetHiddenShortcut(hiddenShortcutIndex);
			
		if (hiddenShortcut->titleIndex == CFG_MENU_TITLE_INDEX) 
		{
			exitToSystemMenu();
		} else if(hiddenShortcut->titleIndex == CFG_BOOTMII_TITLE_INDEX) 
		{
			launchBootmii();
		}
		else 
		{
			Title * title;
			title = GetTitle(hiddenShortcut->titleIndex);
			launchCrazyTitle(title);
		}
	}
	else 
	{	
		//Check for autoboot, if mp3 finishes playing then autoboot is due
		if (doingAutoboot)
		{
			#ifdef DEBUG 
				Syslog_Send(syslog, SYSLOG_PRI_INTERNAL, SYSLOG_SEV_DEBUG, "syslog: doing autoboot");		
			#endif
		
			s8 autobootTitleIndex = GetAutobootTitleIndex();
			
			if (autobootTitleIndex == CFG_MENU_TITLE_INDEX) 
			{
				#ifdef DEBUG 
					Syslog_Send(syslog, SYSLOG_PRI_INTERNAL, SYSLOG_SEV_DEBUG, "syslog: autobooting menu");		
				#endif			
				exitToSystemMenu();
			} 
			else if (autobootTitleIndex == CFG_BOOTMII_TITLE_INDEX) 
			{
				launchBootmii();
			}
			else 
			{
				#ifdef DEBUG 
					Syslog_Send(syslog, SYSLOG_PRI_INTERNAL, SYSLOG_SEV_DEBUG, "syslog: autobooting configured title");		
				#endif			
			
				Title * title;
				title = GetTitle(autobootTitleIndex);
				launchCrazyTitle(title);
			}
		}
	
		//If we are not doing autoboot then take note of the defined shortcuts
		if (!doingAutoboot) {
			#ifdef DEBUG 
				Syslog_SendFormat(syslog, SYSLOG_PRI_INTERNAL, SYSLOG_SEV_DEBUG, "syslog: booting selected shortcut, button pressed : %d", currentKey);		
			#endif			
			s8 selectedShortcutIndex = GetShortcutIndexByKey(currentKey);

			#ifdef DEBUG 
				Syslog_SendFormat(syslog, SYSLOG_PRI_INTERNAL, SYSLOG_SEV_DEBUG, "syslog: selected shortcut index is : %d", selectedShortcutIndex);		
			#endif			

		
			if (selectedShortcutIndex ==  CFG_MENU_TITLE_INDEX) 
			{
				#ifdef DEBUG 
					Syslog_Send(syslog, SYSLOG_PRI_INTERNAL, SYSLOG_SEV_DEBUG, "syslog: booting menu");
				#endif			
			
				exitToSystemMenu();
			}
			else if (selectedShortcutIndex ==  CFG_BOOTMII_TITLE_INDEX) 
			{
				launchBootmii();
			}
			else 
			{
				#ifdef DEBUG 
					Syslog_Send(syslog, SYSLOG_PRI_INTERNAL, SYSLOG_SEV_DEBUG, "syslog: shortcut is not menu");
				#endif			
			
				//Get the actual shortcut and it's respective title
				Shortcut * shortcut;		
				shortcut = GetShortcut(selectedShortcutIndex);
				
				if (shortcut->titleIndex == CFG_MENU_TITLE_INDEX) 
				{
					#ifdef DEBUG 
						Syslog_SendFormat(syslog, SYSLOG_PRI_INTERNAL, SYSLOG_SEV_DEBUG, "syslog: selected shortcut seems to be menu : %d", shortcut->titleIndex);
					#endif			
				
					exitToSystemMenu();				
				} else if (shortcut->titleIndex == CFG_BOOTMII_TITLE_INDEX) 
				{
					#ifdef DEBUG 
						Syslog_SendFormat(syslog, SYSLOG_PRI_INTERNAL, SYSLOG_SEV_DEBUG, "syslog: selected shortcut seems to be bootmii : %d", shortcut->titleIndex);
					#endif		
				
					launchBootmii();
				}
				{
					#ifdef DEBUG 
						Syslog_SendFormat(syslog, SYSLOG_PRI_INTERNAL, SYSLOG_SEV_DEBUG, "syslog: selected shortcut is not menu : %d", shortcut->titleIndex);
					#endif			
				
					Title * title;
					title = GetTitle(shortcut->titleIndex);
					
					
					launchCrazyTitle(title);				
				}
			}
		}
	}
}